package com.netx.ebs;
import javax.servlet.http.Cookie;
import java.io.IOException;
import com.netx.data.Connection;
import com.netx.data.DatabaseException;


public abstract class AuthenticatorServlet extends EbsServlet {

	protected AuthenticatorServlet() {
		super();
	}

	// 1.Actions during login/logout procedures:
	protected abstract void onLogin(EbsRequest request, EbsResponse response) throws IOException, DatabaseException;
	protected abstract void onLogout(EbsRequest request, EbsResponse response, String username) throws IOException, DatabaseException;
	protected abstract void onErrorFailed(EbsRequest request, EbsResponse response, String username) throws IOException, DatabaseException;
	protected abstract void onErrorAborted(EbsRequest request, EbsResponse response, String username) throws IOException, DatabaseException;
	protected abstract void onErrorUnknownAddress(EbsRequest request, EbsResponse response) throws IOException, DatabaseException;
	// 2.Actions during application use:
	protected abstract void onNotAuthenticated(EbsRequest request, EbsResponse response) throws IOException, DatabaseException;
	protected abstract void onSessionTerminated(EbsRequest request, EbsResponse response, String username, NOTIFICATION reason, String message) throws IOException, DatabaseException;
	
	public final void doGet(EbsRequest request, EbsResponse response) throws IOException, DatabaseException {
		final String action = request.getParameter("action");
		// [authenticator]
		if(action == null) {
			onLogin(request, response);
		}
		// [authenticator]?action=logout
		else if(action.equals("logout")) {
			// make sure a session exists:
			Cookie[] cookies = Cookies.getCookies(request);
			if(cookies[1] == null) {
				response.sendRedirect(getEbsContext().getAuthenticatorName());
			}
			else {
				// terminate session:
				Session s = getEbsContext().internalGetSessionManager().getSession(cookies[0].getValue());
				final String username = s.getUser().getUsername();
				getEbsContext().internalGetSessionManager().terminateSession(s, NOTIFICATION.LOGOUT, null);
				s = null;
				request.removeAttribute(Constants.REQUEST_SESSION);
				// delete user-ID cookie:
				Cookies.deleteUserID(response);
				onLogout(request, response, username);
			}
		}
		else if(action.equals("error")) {
			final String reason = request.getParameter("reason", true);
			// [authenticator]?action=error&reason=failed&user=<username>
			if(reason.equals("failed")) {
				String user = request.getParameter("user", true);
				onErrorFailed(request, response, user);
			}
			// [authenticator]?action=error&reason=aborted&user=<username>
			else if(reason.equals("aborted")) {
				String user = request.getParameter("user", true);
				onErrorAborted(request, response, user);
			}
			// [authenticator]?action=error&reason=unknown-address
			else if(reason.equals("unknown-address")) {
				onErrorUnknownAddress(request, response);
			}
			// [authenticator]?action=error&reason=no-auth
			else if(reason.equals("no-auth")) {
				onNotAuthenticated(request, response);
			}
			// [authenticator]?action=error&reason=session-terminated&user=<username>&notification=<n>&message=<message>
			else if(reason.equals("session-terminated")) {
				String user = request.getParameter("user", true);
				String notification = request.getParameter("notification", true);
				try {
					NOTIFICATION n = NOTIFICATION.valueOf(notification.toUpperCase());
					String message = request.getParameter("message", false);
					onSessionTerminated(request, response, user, n, message);
				}
				catch(IllegalArgumentException iae) {
					throw new IllegalOperationException("illegal 'notification' parameter value: "+notification, request);
				}
			}
			else {
				throw new IllegalOperationException("illegal 'reason' parameter value: "+reason, request);
			}
		}
		else {
			throw new IllegalOperationException("illegal 'action' parameter value: "+action, request);
		}
	}

	public final void doPost(EbsRequest request, EbsResponse response) throws IOException, DatabaseException {
		EbsContext ebsCtx = getEbsContext(); 
		Connection c = request.getDatabaseConnection();
		final User user;
		if(ebsCtx.getSessionMode().equals("fixed-ip")) {
			user = Entities.getUsers(c).findByAddress(request.getRemoteAddr());
			c.close();
			if(user == null) {
				response.sendRedirect(ebsCtx.getAuthenticatorName()+"?action=error&reason=unknown-address");
				return;
			}
		}
		else {
			final String username = request.getParameter("username", true);
			final String password = request.getParameter("password", true);
			user = Entities.getUsers(c).find(username, password);
			c.close();
			if(user == null) {
				response.sendRedirect(ebsCtx.getAuthenticatorName()+"?action=error&reason=failed&user="+username);
				return;
			}
		}
		// User found, continue login:
		Cookie[] cookies = Cookies.getCookies(request);
		if(cookies[0] == null) {
			cookies[0] = Cookies.createClientID();
		}
		cookies[1] = Cookies.createUserID(user.getPrimaryKey());
		String clientID = cookies[0].getValue();

		// 1. check for login in different machines (abort):
		SessionManager sm = ebsCtx.internalGetSessionManager();
		Session session = sm.getSession(user.getPrimaryKey());
		if(session != null) {
			if(!session.getClientID().equals(clientID)) {
				sm.terminateSession(session, NOTIFICATION.ABORTED, null);
				Cookies.deleteUserID(response);
				response.sendRedirect(ebsCtx.getAuthenticatorName()+"?action=error&reason=aborted&user="+user.getUsername());
				return;
			}
		}
		// 2. destroy previous sessions from this machine:
		session = sm.getSession(clientID);
		boolean sameLogin = false;
		if(session != null) {
			if(session.getUser().getPrimaryKey() == user.getPrimaryKey()) {
				sameLogin = true;
			}
			else {
				sm.terminateSession(session, NOTIFICATION.LOGOUT, null);
			}
		}
		// 3. create session and clear notifications:
		if(!sameLogin) {
			session = sm.createSession(clientID, request.getRemoteAddr(), user);
		}
		sm.clearNotificationsFor(clientID);
		Cookies.refreshCookies(cookies, user, response);
		response.sendRedirect(user.getRole().getLoginResource());
	}
}
